package compile

import (
	"bytes"
	"encoding/hex"
	"fmt"
	"math/big"
	"os"
	"path"
	"strings"

	"gitee.com/caivega/easm/applet"
	"gitee.com/caivega/easm/core"
	"gitee.com/caivega/easm/util"
	"github.com/ethereum/go-ethereum/accounts/abi"
	"github.com/ethereum/go-ethereum/common"
	"github.com/ethereum/go-ethereum/core/vm"
	"github.com/holiman/uint256"
)

func Generate(current *Current) []byte {
	fmt.Println("-------------------------")
	mb := core.CreateBuffer() // main buffer
	codeSection, ok := findLabel(current, SECTION_CODE)
	if ok && codeSection.IsLabelType(core.LABEL_SECTION) {
		labels := listFunction(codeSection)
		for i := 0; i < len(labels); i++ {
			fl := labels[i]
			if fl.n == ASM_MAIN {
				generateFunction(current, 0, mb, fl, fl)
				break
			}
		}
		for i := 0; i < len(labels); i++ {
			fl := labels[i]
			if fl.n != ASM_MAIN {
				fl.f.postion = mb.Len()
				mb.Append([]byte{byte(vm.JUMPDEST)})
				generateFunction(current, 0, mb, fl, fl)
				current.updateFuncs = append(current.updateFuncs, fl.f)
			}
		}
	}
	callSection, ok := findLabel(current, SECTION_CALL)
	if ok && callSection.IsLabelType(core.LABEL_SECTION) {
		labels := listLabels(callSection)
		for i := 0; i < len(labels); i++ {
			fl := labels[i]
			fl.f.postion = mb.Len()
			mb.Append([]byte{byte(vm.JUMPDEST)})
			generateFunction(current, 0, mb, fl, fl)
			current.updateFuncs = append(current.updateFuncs, fl.f)
		}

		labels = listFunction(callSection)
		for i := 0; i < len(labels); i++ {
			fl := labels[i]
			fl.f.postion = mb.Len()
			mb.Append([]byte{byte(vm.JUMPDEST)})
			generateFunction(current, 0, mb, fl, fl)
			current.updateFuncs = append(current.updateFuncs, fl.f)
		}
	}
	for i := 0; i < len(current.updateFuncs); i++ {
		f := current.updateFuncs[i]
		updateCall(current, f)
	}
	code := mb.Bytes()
	maxSize := new(uint256.Int).Exp(uint256.NewInt(2), uint256.NewInt(uint64(current.config.LabelSize*8)))
	if len(code) > int(maxSize.Uint64()) {
		panic("error max size")
	}
	if current.IsDebug() {
		core.DumpCode(code)
	}
	return code
}

func appendCall(current *Current, c *Call, f *Function, cb *core.Buffer, cf *Function) {
	pl := len(c.params)
	if len(f.params) != pl {
		panic("error parameters for function: " + c.name)
	}
	// TODO verify parameters for function
	// for i := 0; i < pl; i++ {
	// 	fp := cf.params[i]
	// 	cp := call.params[i]
	// }

	op := getPush(current.config.LabelSize)
	cb.Append([]byte{byte(op)})
	backOffset := cb.Len()
	cb.Append(bytes.Repeat([]byte{0x0}, current.config.LabelSize)) // keep the blank for update back

	cf.stackOffset++ // as the back(+1)
	generateParameters(current, cb, cf, c.params)
	cf.stackOffset-- //as the back(-1)

	op = getPush(current.config.LabelSize)
	cb.Append([]byte{byte(op)})

	c.cb = cb
	c.offset = cb.Len()
	f.calls = append(f.calls, c)

	cb.Append(bytes.Repeat([]byte{0x0}, current.config.LabelSize)) // keep the blank for update call
	cb.Append([]byte{byte(vm.JUMP)})
	back := cb.Len()
	cb.Append([]byte{byte(vm.JUMPDEST)})

	backData := getPostionData(current, back)
	cb.Write(backOffset, backData)
}

func updateCall(current *Current, f *Function) {
	ps := getPostionData(current, f.postion)
	for i := 0; i < len(f.calls); i++ {
		c := f.calls[i]
		c.cb.Write(c.offset, ps)
	}
}

func generatePush(v string) []byte {
	n, c := getInt(v)
	if c > 32 {
		panic("too large int")
	}
	if c == 0 {
		return []byte{byte(vm.PUSH0)}
	}
	buf := new(bytes.Buffer)
	if n.Sign() == -1 && len(n.Abs(n).Bytes()) <= 16 { // special for less code size
		n = n.Abs(n)
		c = len(n.Bytes())

		err := buf.WriteByte(byte(getPush(c)))
		if err != nil {
			panic(err)
		}
		nc, err := buf.Write(n.Bytes())
		if err != nil {
			panic(err)
		}
		if nc != c {
			panic("error int")
		}
		buf.Write([]byte{
			byte(vm.PUSH0), byte(vm.SUB),
		})
	} else {
		err := buf.WriteByte(byte(getPush(c)))
		if err != nil {
			panic(err)
		}
		nc, err := buf.Write(n.Bytes())
		if err != nil {
			panic(err)
		}
		if nc != c {
			panic("error int")
		}
	}

	return buf.Bytes()
}

func generatePushN(v string, N int) []byte {
	n, c := getInt(v)
	if c > 32 {
		panic("too large int")
	}
	if c > N {
		panic("error push")
	}
	buf := new(bytes.Buffer)
	if c == 0 {
		err := buf.WriteByte(byte(getPush(c)))
		if err != nil {
			panic(err)
		}
		nc, err := buf.Write(bytes.Repeat([]byte{0x0}, N))
		if err != nil {
			panic(err)
		}
		if nc != N {
			panic("error int")
		}
	} else if n.Sign() == -1 && len(n.Abs(n).Bytes()) <= 16 { // special for less code size
		n = n.Abs(n)
		c = len(n.Bytes())

		err := buf.WriteByte(byte(getPush(c)))
		if err != nil {
			panic(err)
		}
		nc, err := buf.Write(util.PaddingZeroInLeft(n.Bytes(), N))
		if err != nil {
			panic(err)
		}
		if nc != N {
			panic("error int")
		}
		buf.Write([]byte{
			byte(vm.PUSH0), byte(vm.SUB),
		})
	} else {
		err := buf.WriteByte(byte(getPush(c)))
		if err != nil {
			panic(err)
		}
		nc, err := buf.Write(util.PaddingZeroInLeft(n.Bytes(), N))
		if err != nil {
			panic(err)
		}
		if nc != N {
			panic("error int")
		}
	}

	return buf.Bytes()
}

func generatePushBytes(data []byte) []byte {
	n, c := getBytes(data)
	if c > 32 {
		panic("too large int")
	}
	if c == 0 {
		return []byte{byte(vm.PUSH0)}
	}
	buf := new(bytes.Buffer)
	err := buf.WriteByte(byte(getPush(c)))
	if err != nil {
		panic(err)
	}
	nc, err := buf.Write(n.Bytes())
	if err != nil {
		panic(err)
	}
	if nc != c {
		panic("error int")
	}

	return buf.Bytes()
}

// return the count of the used stack items(pushs)
func generateExpression(current *Current, cb *core.Buffer, cf *Function, value string) {
	items := processExpression(current, value)
	ll := len(items)
	for i := 0; i < ll; i++ {
		offset := generateOperator(current, cb, cf, items[i])
		cf.stackOffset += offset
	}
}

// return the count of the used stack items(pushs)
func generateParameters(current *Current, cb *core.Buffer, cf *Function, params []*Label) int {
	offset := 0

	ll := len(params)
	isAllCallData := true
	for i := 0; i < ll; i++ {
		cp := params[i]
		if cp.IsLabelType(core.LABEL_CALL_DATA) {
			if cp.d.callDataIndex != i {
				panic("error call data")
			}
		} else {
			isAllCallData = false
			break
		}
	}
	if isAllCallData {
		if ll > 0 {
			cb.Append([]byte{
				byte(vm.PUSH1), 0x4,
			})
			for i := 0; i < ll; i++ {
				cp := params[i]

				switch cp.d.t {
				case core.DATA_INT:
					cb.Append([]byte{
						byte(vm.DUP1),
						byte(vm.CALLDATALOAD),
						byte(vm.SWAP1),
					})
					if i == (ll - 1) {
						cb.Append([]byte{
							byte(vm.POP),
						})
					} else {
						cb.Append([]byte{
							byte(vm.PUSH1), 0x20,
							byte(vm.ADD),
						})
					}
					offset++
				}
			}
		}
	} else {
		for i := 0; i < ll; i++ {
			cp := params[i]
			switch cp.t {
			case core.LABEL_VALUE:
				switch cp.d.t {
				case core.DATA_INT:
					v, ok := cp.d.attrs[core.ATTRIBUTE_VALUE_NAME]
					if !ok {
						panic("error int value")
					}
					if isExpression(v) {
						generateExpression(current, cb, cf, v)
						offset++
					} else {
						cb.Append(generatePush(v))
						offset++
					}
				default:
					panic("unsupported value")
				}
			case core.LABEL_LOCAL, core.LABEL_PARAMETER:
				cf := currentFunc(current)
				op := getDup(cf.f.Offset() - cp.d.localIndex + offset)
				cb.Append([]byte{byte(op)})
				offset++
			case core.LABEL_STACK:
				//
			case core.LABEL_SLOT:
				cb.Append(generatePush(fmt.Sprintf("%d", cp.d.slotIndex)))
				cb.Append([]byte{
					byte(vm.SLOAD),
				})
				offset++
			case core.LABEL_VAR:
				cb.Append(generatePush(fmt.Sprintf("%d", cp.d.slotIndex)))
				cb.Append([]byte{
					byte(vm.MLOAD),
				})
				offset++
			case core.LABEL_LABEL:
				op := getPush(current.config.LabelSize)
				cb.Append([]byte{byte(op)})

				c := &Call{}
				c.cb = cb
				c.offset = cb.Len()
				cp.f.calls = append(cp.f.calls, c)

				cb.Append(bytes.Repeat([]byte{0x0}, current.config.LabelSize)) // keep the blank label

				offset++
			}
		}
	}
	return offset
}

func generatePushWithParameters(c *Current, cb *core.Buffer, cf *Function, params []*Label, N int) int {
	offset := 0

	ll := len(params)
	if N == 0 {
		if ll != 0 {
			panic("error push0")
		}
		cb.Append([]byte{
			byte(vm.PUSH0),
		})
		offset++
	} else {
		for i := 0; i < ll; i++ {
			cp := params[i]
			switch cp.t {
			case core.LABEL_VALUE:
				switch cp.d.t {
				case core.DATA_INT:
					v, ok := cp.d.attrs[core.ATTRIBUTE_VALUE_NAME]
					if !ok {
						panic("error int value")
					}
					cb.Append(generatePushN(v, N))
					offset++
				default:
					panic("unsupported value")
				}
			case core.LABEL_LOCAL, core.LABEL_PARAMETER:
				op := getDup(cf.Offset() - cp.d.localIndex + offset)
				cb.Append([]byte{byte(op)})
				offset++
			case core.LABEL_SLOT:
				cb.Append(generatePushN(fmt.Sprintf("%d", cp.d.slotIndex), N))
				cb.Append([]byte{
					byte(vm.SLOAD),
				})
				offset++
			case core.LABEL_VAR:
				cb.Append(generatePushN(fmt.Sprintf("%d", cp.d.slotIndex), N))
				cb.Append([]byte{
					byte(vm.MLOAD),
				})
				offset++
			}
		}
	}
	return offset
}

func generatePrint(current *Current, cb *core.Buffer, cf *Function, params []*Label) (int, int) {
	ll := len(params)
	if ll == 0 {
		//save the top of the stack to the memory
		cb.Append(generatePush(fmt.Sprintf("%d", current.memory.offset*0x20)))
		cb.Append([]byte{byte(vm.MSTORE)})
		return current.memory.offset * 0x20, 0x20
	}
	generateParameters(current, cb, cf, params)
	for i := 0; i < ll; i++ {
		cb.Append(generatePush(fmt.Sprintf("%d", (current.memory.offset+i)*0x20)))
		cb.Append([]byte{byte(vm.MSTORE)})
	}
	return current.memory.offset * 0x20, 0x20 * ll
}

func generateStore(c *Current, cb *core.Buffer, cf *Function, params []*Label) {
	ll := len(params)
	switch ll {
	case 1:
		cp := params[0]
		switch cp.t {
		case core.LABEL_LOCAL:
			op := getSwap(cf.Offset() - cp.d.localIndex)
			cb.Append([]byte{byte(op)})
			cb.Append([]byte{byte(vm.POP)})

		}
	case 2:
		cp := params[0]
		switch cp.t {
		case core.LABEL_SLOT:
			switch cp.d.t {
			case core.DATA_INT:
				generateParameters(c, cb, cf, params[1:])

				cb.Append(generatePush(fmt.Sprintf("%d", cp.d.slotIndex)))
				cb.Append([]byte{
					byte(vm.SSTORE),
				})

			}
		case core.LABEL_VAR:
			switch cp.d.t {
			case core.DATA_INT:
				generateParameters(c, cb, cf, params[1:])

				cb.Append(generatePush(fmt.Sprintf("%d", cp.d.varIndex)))
				cb.Append([]byte{
					byte(vm.MSTORE),
				})

			}
		case core.LABEL_LOCAL, core.LABEL_PARAMETER:
			switch cp.d.t {
			case core.DATA_INT:
				generateParameters(c, cb, cf, params[1:])

				op := getSwap(cf.Offset() - cp.d.localIndex)
				cb.Append([]byte{byte(op)})
				cb.Append([]byte{byte(vm.POP)})
			}
		}
	}
}

// generate call interfaces which same with solidity
func generateArguments(c *Current, arguments []*Label) abi.Arguments {
	list := make(abi.Arguments, 0)
	for _, a := range arguments {
		switch a.d.t {
		case core.DATA_INT:
			t, err := abi.NewType("int256", "", nil)
			if err != nil {
				panic(err)
			}
			list = append(list, abi.Argument{Name: a.n, Type: t, Indexed: false})
		}
	}
	return list
}

func generateInterface(current *Current, cb *core.Buffer, mfl *Label) {
	callSection, ok := findLabel(current, SECTION_CALL)
	if ok && callSection.IsLabelType(core.LABEL_SECTION) {
		at := applet.CreateApplet(current.config, cb)
		at.PushCall("switch").AppendCode(vm.JUMP)
		at.CreateLabel("exit").Append([]byte{
			byte(vm.PUSH1), 0x20,
			byte(vm.PUSH0), byte(vm.RETURN),
		})
		at.CreateLabel("switch").Append([]byte{
			byte(vm.PUSH0), byte(vm.CALLDATALOAD), // load calldata[0:32]
			byte(vm.PUSH1), 0xe0, byte(vm.SHR), // get the four bytes for call function
		})

		cm := currentModule(current)
		sb := &strings.Builder{}
		sb.WriteString("// SPDX-License-Identifier: UNLICENSED\n")
		sb.WriteString("pragma solidity ^0.8.0;\n")
		sb.WriteString("\n")
		sb.WriteString(fmt.Sprintf("contract %s {\n", cm.n))

		labels := listFunction(callSection)
		for i := 0; i < len(labels); i++ {
			fl := labels[i]

			inputs := generateArguments(current, fl.f.params)
			outputs := generateArguments(current, fl.f.returns)
			m := abi.NewMethod(fl.n, fl.n, abi.Function, "", false, false, inputs, outputs)
			fmt.Println("> interface", m.Sig, hex.EncodeToString(m.ID))

			sb.WriteString("\n")
			sb.WriteString("  function ")
			sb.WriteString(m.Name)

			sb.WriteString("(")
			ll := len(inputs)
			if ll > 0 {
				sb.WriteString(inputs[0].Type.String() + " " + inputs[0].Name)
				for j := 1; j < ll; j++ {
					sb.WriteString(", ")
					sb.WriteString(inputs[j].Type.String() + " " + inputs[j].Name)
				}
			}
			sb.WriteString(") public pure")

			ll = len(outputs)
			if ll > 0 {
				sb.WriteString(" returns(")
				sb.WriteString(outputs[0].Type.String())
				for j := 1; j < ll; j++ {
					sb.WriteString(", ")
					sb.WriteString(outputs[j].Type.String())
				}
				sb.WriteString(")")
			}
			sb.WriteString(" {")
			if ll > 0 {
				sb.WriteString("\n")
				sb.WriteString("    return")
				sb.WriteString(" 0")
				for j := 1; j < ll; j++ {
					sb.WriteString(", ")
					sb.WriteString("0")
				}
				sb.WriteString(";\n  ")
			}
			sb.WriteString("}\n")

			at.Append([]byte{
				byte(vm.DUP1),
			})
			at.Append(generatePush("0x" + hex.EncodeToString(m.ID)))
			at.Append([]byte{
				byte(vm.EQ),
			})
			at.PushCall(fl.n).AppendCode(vm.JUMPI)
		}
		sb.WriteString("}\n")

		at.CreateLabel("back")
		at.AppendCode(vm.POP).PushCall("exit").AppendCode(vm.JUMP)
		for i := 0; i < len(labels); i++ {
			fl := labels[i]

			ll := len(fl.f.params)
			callParameters := make([]*Label, ll)
			for j := 0; j < ll; j++ {
				fp := fl.f.params[j]
				cp := &Label{n: fp.n, t: core.LABEL_CALL_DATA, attrs: make(map[string]string), d: createCallData(fp.d.t, j)}
				callParameters[j] = cp
			}
			call := &Call{name: fl.n, params: callParameters}

			at.CreateLabel(fl.n)
			appendCall(current, call, fl.f, cb, mfl.f)

			switch len(fl.f.returns) {
			case 0:
				at.Append([]byte{
					byte(vm.PUSH0),
					byte(vm.PUSH0),
					byte(vm.MSTORE),
				})
			case 1:
				at.Append([]byte{
					byte(vm.PUSH0),
					byte(vm.MSTORE),
				})
			default:
				panic("unsupported")
			}
			at.PushCall("back").AppendCode(vm.JUMP)
		}
		at.Update()
		if current.IsDebug() {
			moduleDir := path.Dir(cm.m.filePath)
			fileName := path.Base(cm.m.filePath)
			moduleName := fileName[0 : len(fileName)-len(ASM_EXT)]
			solPath := path.Join(moduleDir, moduleName+".sol")
			err := os.WriteFile(solPath, []byte(sb.String()), os.ModePerm)
			if err != nil {
				panic(err)
			}
		}
	} else {
		generateReturn(current, cb, mfl, nil)
	}
}

func generateReturn(c *Current, cb *core.Buffer, fl *Label, returnParameters []*Label) {
	cf := fl.f
	offset := 1 // as the call back(+1)
	if fl.n == ASM_MAIN {
		offset = 0
	}
	returnList := make([]byte, 0)
	if len(returnParameters) > 0 {
		parameterCount := generateParameters(c, cb, cf, returnParameters)
		opList := make([]byte, 0)
		if offset > 0 {
			if cf.localCount > 0 {
				cb.Append([]byte{byte(getDup(cf.Offset() + parameterCount + offset))})
				for i := 0; i < parameterCount; i++ {
					opList = append(opList, byte(getSwap(cf.Offset()+offset)), byte(vm.POP))
				}
			}
			opList = append(opList, byte(getSwap(cf.Offset()+offset)))
		} else {
			for i := 0; i < parameterCount; i++ {
				opList = append(opList, byte(getSwap(cf.Offset())))

				pushBytes := generatePush(fmt.Sprintf("%d", (parameterCount-1-i)*0x20))
				returnList = append(returnList, pushBytes...)
				returnList = append(returnList, byte(vm.MSTORE))
			}

			pushBytes := generatePush(fmt.Sprintf("%d", parameterCount*0x20))
			returnList = append(returnList, pushBytes...)
			returnList = append(returnList, byte(vm.PUSH0))
		}
		cb.Append(opList) // swap return parameters
	} else {
		returnList = append(returnList, byte(vm.PUSH1), 0x20, byte(vm.PUSH0))
	}
	popList := make([]byte, 0)
	for i := 0; i < cf.localCount; i++ {
		popList = append(popList, byte(vm.POP))
	}
	cb.Append(popList) // pop function parameters and locals

	if offset > 0 { // return to the call
		cb.Append([]byte{byte(vm.JUMP)})
	} else { // return to the evm
		cb.Append(returnList)
		cb.Append([]byte{byte(vm.RETURN)})
	}
}

func generateStaticCall(current *Current, cb *core.Buffer, gas int, address common.Address, inOffset, inSize, outOffset, outSize int) {
	if outOffset == outSize && inSize == outSize && inOffset == outSize {
		cb.Append(generatePush(fmt.Sprintf("%d", outSize)))
		cb.Append([]byte{byte(vm.DUP1), byte(vm.DUP1), byte(vm.DUP1)})
	} else {
		if outOffset == inOffset && outSize == inSize {
			cb.Append(generatePush(fmt.Sprintf("%d", outSize)))
			cb.Append(generatePush(fmt.Sprintf("%d", outOffset)))
			cb.Append([]byte{byte(vm.DUP2), byte(vm.DUP2)})
		} else {
			cb.Append(generatePush(fmt.Sprintf("%d", outSize)))
			cb.Append(generatePush(fmt.Sprintf("%d", outOffset)))
			cb.Append(generatePush(fmt.Sprintf("%d", inSize)))
			cb.Append(generatePush(fmt.Sprintf("%d", inOffset)))
		}
	}
	pushBytes := generatePushBytes(address.Bytes())
	cb.Append(pushBytes)
	if gas == -1 {
		cb.Append([]byte{byte(vm.GAS)})
	} else {
		cb.Append(generatePush(fmt.Sprintf("%d", gas)))
	}
	cb.Append([]byte{byte(vm.STATICCALL)})
	cb.Append([]byte{byte(vm.POP)})
}

func orderArguments(item *OperatorItem, reverse bool) ([]*Label, bool) {
	ll := len(item.params)

	t := int(item.t)
	leftCount := ((int(t) % 10000) - (int(t) % 1000)) / 1000
	rightCount := ((int(t) % 1000) - (int(t) % 100)) / 100
	if ll != (leftCount + rightCount) {
		panic("error operator: " + item.t.String())
	}
	if ll == 1 {
		return []*Label{item.params[0].value}, false
	}
	params := make([]*Label, ll)
	if !reverse {
		for i := 0; i < ll; i++ {
			params[i] = item.params[i].value
		}
		return params, false
	}
	needSwap := false
	for i := 0; i < ll; i++ {
		ip := item.params[ll-1-i]
		if ip.value.IsLabelType(core.LABEL_STACK) && (i == (ll - 1)) {
			needSwap = true
		}
		params[i] = ip.value
	}
	return params, needSwap
}

func appendOperator(cb *core.Buffer, ops []vm.OpCode, needSwap bool) {
	if needSwap {
		cb.Append([]byte{byte(vm.SWAP1)})
	}
	ll := len(ops)
	data := make([]byte, ll)
	for i := 0; i < ll; i++ {
		data[i] = byte(ops[i])
	}
	cb.Append(data)
}

func generateOperator(current *Current, cb *core.Buffer, cf *Function, item *OperatorItem) int {
	offset := 0
	switch item.t {
	case core.OPERATOR_EQUALS:
		params, needSwap := orderArguments(item, false)
		offset += generateParameters(current, cb, cf, params)
		appendOperator(cb, []vm.OpCode{vm.EQ}, needSwap)
		offset--

	case core.OPERATOR_NOT_EQUALS:
		params, needSwap := orderArguments(item, false)
		offset += generateParameters(current, cb, cf, params)
		appendOperator(cb, []vm.OpCode{vm.EQ, vm.ISZERO}, needSwap)
		offset--

	case core.OPERATOR_ANDS:
		params, needSwap := orderArguments(item, false)
		offset += generateParameters(current, cb, cf, params)
		appendOperator(cb, []vm.OpCode{vm.AND}, needSwap)
		offset--

	case core.OPERATOR_ORS:
		params, needSwap := orderArguments(item, false)
		offset += generateParameters(current, cb, cf, params)
		appendOperator(cb, []vm.OpCode{vm.OR}, needSwap)
		offset--

	case core.OPERATOR_LARGER:
		params, needSwap := orderArguments(item, true)
		offset += generateParameters(current, cb, cf, params)
		appendOperator(cb, []vm.OpCode{vm.GT}, needSwap)
		offset--

	case core.OPERATOR_LARGER_AND_EQUAL:
		params, needSwap := orderArguments(item, true)
		offset += generateParameters(current, cb, cf, params)
		appendOperator(cb, []vm.OpCode{vm.LT, vm.ISZERO}, needSwap)
		offset--

	case core.OPERATOR_LESS:
		params, needSwap := orderArguments(item, true)
		offset += generateParameters(current, cb, cf, params)
		appendOperator(cb, []vm.OpCode{vm.LT}, needSwap)
		offset--

	case core.OPERATOR_LESS_AND_EQUAL:
		params, needSwap := orderArguments(item, true)
		offset += generateParameters(current, cb, cf, params)
		appendOperator(cb, []vm.OpCode{vm.GT, vm.ISZERO}, needSwap)
		offset--

	case core.OPERATOR_EQUAL:
		params, _ := orderArguments(item, false)
		generateStore(current, cb, cf, params)

	case core.OPERATOR_PLUSES:
		params, _ := orderArguments(item, false)
		generateParameters(current, cb, cf, params)
		cb.Append([]byte{
			byte(vm.PUSH1), 0x1,
			byte(vm.ADD),
		})
		generateStore(current, cb, cf, params)

	case core.OPERATOR_ADD:
		params, _ := orderArguments(item, false)
		offset += generateParameters(current, cb, cf, params)
		cb.Append([]byte{
			byte(vm.ADD),
		})
		offset--

	case core.OPERATOR_SUB:
		params, needSwap := orderArguments(item, true)
		offset += generateParameters(current, cb, cf, params)
		appendOperator(cb, []vm.OpCode{vm.SUB}, needSwap)
		offset--

	case core.OPERATOR_MUL:
		params, _ := orderArguments(item, false)
		offset += generateParameters(current, cb, cf, params)
		cb.Append([]byte{
			byte(vm.MUL),
		})
		offset--

	case core.OPERATOR_DIV:
		params, needSwap := orderArguments(item, true)
		offset += generateParameters(current, cb, cf, params)
		appendOperator(cb, []vm.OpCode{vm.DIV}, needSwap)
		offset--

	case core.OPERATOR_AND:
		params, needSwap := orderArguments(item, true)
		offset += generateParameters(current, cb, cf, params)
		appendOperator(cb, []vm.OpCode{vm.AND}, needSwap)
		offset--

	case core.OPERATOR_OR:
		params, needSwap := orderArguments(item, true)
		offset += generateParameters(current, cb, cf, params)
		appendOperator(cb, []vm.OpCode{vm.OR}, needSwap)
		offset--

	case core.OPERATOR_EXP:
		params, needSwap := orderArguments(item, true)
		offset += generateParameters(current, cb, cf, params)
		appendOperator(cb, []vm.OpCode{vm.EXP}, needSwap)
		offset--

	case core.OPERATOR_NOT:
		params, _ := orderArguments(item, true)
		offset += generateParameters(current, cb, cf, params)
		cb.Append([]byte{
			byte(vm.NOT),
		})

	}
	return offset
}

func getNext(current int, l *Label) *Label {
	for i := (current + 1); i < len(l.b.nameList); i++ {
		name := l.b.nameList[i]
		l, ok := l.b.nameMap[name]
		if !ok {
			panic("error next label")
		}
		if l.i != nil && l.i.name != core.BLOCK_BODY {
			return l
		}
	}
	panic("no next")
}

func getLast(l *Label) *Label {
	name := l.b.nameList[len(l.b.nameList)-1]
	l, ok := l.b.nameMap[name]
	if !ok {
		panic("error last label")
	}
	return l
}

func generateIf(current *Current, t int, cb *core.Buffer, funcLabel *Label, ifLabel *Label) {
	items := processExpression(current, ifLabel.i.value)
	ll := len(items)
	for i := 0; i < ll; i++ {
		generateOperator(current, cb, funcLabel.f, items[i])
	}

	at := applet.CreateApplet(current.config, cb)
	at.AppendCode(vm.ISZERO)

	next := getNext(-1, ifLabel)
	at.PushCall(next.n).AppendCode(vm.JUMPI)

	last := getLast(ifLabel)

	for i := 0; i < len(ifLabel.b.nameList); i++ {
		name := ifLabel.b.nameList[i]
		l, ok := ifLabel.b.nameMap[name]
		if !ok {
			panic("error label in if")
		}
		switch l.i.name {
		case core.BLOCK_BODY:
			generateFunction(current, t+1, cb, funcLabel, l)
			at.PushCall(last.n).AppendCode(vm.JUMP)

		case core.BLOCK_ELSEIF:
			at.CreateLabel(l.n)

			items := processExpression(current, l.i.value)
			ll := len(items)
			for i := 0; i < ll; i++ {
				generateOperator(current, cb, funcLabel.f, items[i])
			}
			at.AppendCode(vm.ISZERO)

			next := getNext(i, ifLabel)
			at.PushCall(next.n).AppendCode(vm.JUMPI)

			generateFunction(current, t+1, cb, funcLabel, l)
			at.PushCall(last.n).AppendCode(vm.JUMP)

		case core.BLOCK_ELSE:
			at.CreateLabel(l.n)

			generateFunction(current, t+1, cb, funcLabel, l)
			at.PushCall(last.n).AppendCode(vm.JUMP)

		case core.BLOCK_ENDIF:
			at.CreateLabel(l.n)

		default:
			panic("error label in if")
		}
	}
	at.Update()
	// at.Dump()
}

func generateFor(current *Current, t int, cb *core.Buffer, funcLabel *Label, forLabel *Label) {
	last := getLast(forLabel)

	items := processExpression(current, forLabel.i.value)
	ll := len(items)
	switch ll {
	case 1:
		//
	case 3:
		generateOperator(current, cb, funcLabel.f, items[0])
	default:
		panic("error for")
	}

	at := applet.CreateApplet(current.config, cb)
	at.CreateLabel(forLabel.n)
	switch ll {
	case 1:
		generateOperator(current, cb, funcLabel.f, items[0])
	case 3:
		generateOperator(current, cb, funcLabel.f, items[1])
	}
	at.AppendCode(vm.ISZERO)
	at.PushCall(last.n).AppendCode(vm.JUMPI)

	for i := 0; i < len(forLabel.b.nameList); i++ {
		name := forLabel.b.nameList[i]
		l, ok := forLabel.b.nameMap[name]
		if !ok {
			panic("error label in if")
		}
		switch l.i.name {
		case core.BLOCK_BODY:
			generateFunction(current, t+1, cb, funcLabel, l)
			switch ll {
			case 3:
				generateOperator(current, cb, funcLabel.f, items[2])
			}
			at.PushCall(forLabel.n).AppendCode(vm.JUMP)

		case core.BLOCK_ENDFOR:
			at.CreateLabel(l.n)

		default:
			panic("error label in for")
		}
	}
	at.Update()
	// at.Dump()
}

func generateFunction(current *Current, t int, cb *core.Buffer, funcLabel *Label, blockLabel *Label) {
	current.Reset(blockLabel.b)
	traversalBlock(current, t, funcLabel.b, blockLabel.b, func(c *Current, t int, b *Block) bool {
		if b.label != nil {
			if b.label.f != nil {
				if c.IsDebug() {
					fmt.Println(strings.Repeat("  ", t)+"+", b.label.t.String(), b.label.n, b.label.f.localCount, b.label.f.stackOffset)
				}
			} else {
				if c.IsDebug() {
					fmt.Println(strings.Repeat("  ", t)+"+", b.label.t.String(), b.label.n)
				}
				if b.label.i != nil {
					switch {
					case b.label.IsBlock(core.BLOCK_IF):
						generateIf(current, t, cb, funcLabel, b.label)
						return true
					case b.label.IsBlock(core.BLOCK_FOR):
						generateFor(current, t, cb, funcLabel, b.label)
						return true
					}
				}
			}
		} else {
			if c.IsDebug() {
				fmt.Println("+")
			}
		}
		return false
	}, func(current *Current, t int, b *Block) {
		if b.label != nil {
			if b.label.f != nil {
				if !b.label.f.hasReturn {
					if b.label.n == ASM_MAIN && len(b.label.b.nameList) == 0 { // no main code in text section
						generateInterface(current, cb, b.label)
					} else {
						generateReturn(current, cb, b.label, nil)
					}
				}
			}
		}
	}, func(current *Current, t int, index int, count int, b *Block, l *Label) {
		if l.IsData() {
			if l.IsLabelType(core.LABEL_LOCAL) {
				v, ok := l.d.attrs[core.ATTRIBUTE_VALUE_NAME]
				if !ok {
					v = "0"
				}
				cb.Append(generatePush(v))
			}
		} else {
			if current.IsDebug() {
				fmt.Println(strings.Repeat("  ", t)+"-", l.t.String(), l.n)
			}
			switch l.t {
			case core.LABEL_CALL:
				call := l.c
				fl, ok := findLabel(current, call.name)
				if !ok {
					panic("unknown function: " + call.name)
				}
				if fl.f == nil {
					panic("error function: " + fl.n)
				}
				appendCall(current, call, fl.f, cb, funcLabel.f)
			case core.LABEL_LABEL:
				l.f.postion = cb.Len()
				cb.Append([]byte{
					byte(vm.JUMPDEST),
				})
				current.updateFuncs = append(current.updateFuncs, l.f)

			case core.LABEL_PARAMETER:
				//
			case core.LABEL_STORE:
				generateStore(current, cb, funcLabel.f, l.p.params)
			case core.LABEL_RETURN:
				cf := currentFunc(current)
				if cf != nil {
					generateReturn(current, cb, cf, l.p.params)
				} else {
					cl := currentLabel(current)
					if cl != nil {
						cb.Append([]byte{
							byte(vm.RETURN),
						})
					} else {
						panic("error return")
					}
				}

			case core.LABEL_PRINT:
				inputOffset, inputSize := generatePrint(current, cb, funcLabel.f, l.p.params)
				generateStaticCall(current, cb, 1000, core.PRINT_ADDRESS, inputOffset, inputSize, inputOffset, inputSize)
			case core.LABEL_IFNOT:
				items := processExpression(current, l.i.value)
				ll := len(items)
				for i := 0; i < ll; i++ {
					generateOperator(current, cb, funcLabel.f, items[i])
				}
				cb.Append([]byte{
					byte(vm.ISZERO),
				})
				generateParameters(current, cb, funcLabel.f, l.p.params)
				cb.Append([]byte{
					byte(vm.JUMPI),
				})
			case core.LABEL_PUSH:
				generateParameters(current, cb, funcLabel.f, l.p.params)
				funcLabel.f.stackOffset = 0 // reset the stack offset as the expression
			case core.LABEL_OP:
				op := vm.StringToOp(l.p.name)
				if op.IsPush() {
					generatePushWithParameters(current, cb, funcLabel.f, l.p.params, int(op-vm.PUSH0))
				} else {
					generateParameters(current, cb, funcLabel.f, l.p.params)
					cb.Append([]byte{byte(op)})
				}
			default:
				panic("unknown: " + l.n)
			}
		}
	})
}

func listLabels(fl *Label) []*Label {
	labels := make([]*Label, 0)
	if fl.b != nil {
		for _, n := range fl.b.nameList {
			l, ok := fl.b.nameMap[n]
			if !ok {
				panic("error name: " + n)
			}
			if l.IsLabelType(core.LABEL_LABEL) {
				labels = append(labels, l)
			}
		}
	}
	return labels
}

func listFunction(fl *Label) []*Label {
	labels := make([]*Label, 0)
	if fl.b != nil {
		for _, n := range fl.b.nameList {
			l, ok := fl.b.nameMap[n]
			if !ok {
				panic("error name: " + n)
			}
			if l.IsLabelType(core.LABEL_FUNCTION) {
				labels = append(labels, l)
			}
		}
	}
	return labels
}

func currentLabel(current *Current) *Label { // current label
	b := current.Chain(core.LABEL_LABEL)
	if b != nil && b.label != nil {
		return b.label
	}
	return nil
}

func currentFunc(current *Current) *Label { // current function
	b := current.Chain(core.LABEL_FUNCTION)
	if b != nil && b.label != nil {
		return b.label
	}
	return nil
}

func currentSection(current *Current) *Label { // current section
	b := current.Chain(core.LABEL_SECTION)
	if b != nil && b.label != nil {
		return b.label
	}
	return nil
}

func currentModule(current *Current) *Label { // current module
	b := current.Chain(core.LABEL_MODULE)
	if b != nil && b.label != nil {
		return b.label
	}
	return nil
}

func opCode(ip string) (vm.OpCode, bool) {
	IP := strings.ToUpper(ip)
	op := vm.StringToOp(IP)
	if op == vm.STOP && IP != vm.STOP.String() {
		return op, false
	}
	return op, true
}

func traversalBlock(c *Current, t int, fb *Block, b *Block, beforeBlockFunc func(c *Current, t int, b *Block) bool, afterBlockFunc func(c *Current, t int, b *Block), labelFunc func(c *Current, t int, index int, count int, b *Block, l *Label)) {
	if b != nil {
		if b != fb && c.Top() != b {
			c.Push(b)
		}
		processed := false
		if beforeBlockFunc != nil {
			processed = beforeBlockFunc(c, t, b)
		}
		if !processed {
			ll := len(b.nameList)
			for index, n := range b.nameList {
				l, ok := b.nameMap[n]
				if !ok {
					panic("error name: " + n)
				}
				if l.b != nil {
					traversalBlock(c, t+1, fb, l.b, beforeBlockFunc, afterBlockFunc, labelFunc)
				} else {
					if labelFunc != nil {
						labelFunc(c, t+1, index, ll, b, l)
					}
				}
			}
			if afterBlockFunc != nil {
				afterBlockFunc(c, t, b)
			}
		}
		if b != fb {
			c.Pop()
		}
	}
}

func isString(valueString string) bool {
	return strings.HasPrefix(valueString, "\"") && strings.HasSuffix(valueString, "\"")
}

func getString(current *Current, value string) (string, bool) {
	if isString(value) {
		values := recoverString(current, []string{value})
		return values[0], true
	}
	return "", false
}

func getLineName(n string, line int) string {
	return fmt.Sprintf("%s%s%d", n, core.SEPERATOR_LINE, line)
}

func isInt(value string) (*uint256.Int, int, bool) {
	n, ok := new(big.Int).SetString(value, 0)
	if !ok {
		return nil, -1, false
	}
	tn := new(uint256.Int)
	overflow := tn.SetFromBig(n)
	if overflow {
		panic("overflow")
	}
	return tn, len(tn.Bytes()), true
}

func getInt(value string) (*uint256.Int, int) {
	n, c, ok := isInt(value)
	if !ok {
		panic("error int: " + value)
	}
	return n, c
}

func isName(n string) bool {
	list := []rune(n)
	ll := len(list)
	if ll == 0 {
		return false
	}
	isn := true
	for i := 0; i < ll; i++ {
		item := list[i]
		if !util.IsLetter(item) && !(i != 0 && util.IsNumber(item)) {
			isn = false
			break
		}
	}
	return isn
}

func getBytes(data []byte) (*uint256.Int, int) {
	n := new(uint256.Int).SetBytes(data)
	return n, len(n.Bytes())
}

func getPush(c int) vm.OpCode {
	return vm.OpCode(int(vm.PUSH0) + c)
}

func getDup(c int) vm.OpCode {
	return vm.OpCode(int(vm.DUP1) + c - 1)
}

func getSwap(c int) vm.OpCode {
	return vm.OpCode(int(vm.SWAP1) + c - 1)
}

func getPostionData(c *Current, p int) []byte {
	ps := new(big.Int).SetUint64(uint64(p)).Bytes()
	ll := len(ps)
	if ll > c.config.LabelSize {
		panic("error config.LabelSize")
	}
	if ll < c.config.LabelSize {
		for i := 0; i < (ll - c.config.LabelSize); i++ {
			ps = append([]byte{0x0}, ps...)
		}
	}
	return ps
}

func Dump(c *Current) {
	fmt.Println("-------------------------")
	c.Reset(nil)
	r := c.Root()
	traversalBlock(c, 0, r, r, func(c *Current, t int, b *Block) bool {
		if b.label != nil {
			if b.label.f != nil {
				fmt.Println(strings.Repeat("  ", t)+"+", b.label.t.String(), b.label.n, b.label.f.localCount, b.label.f.stackOffset)
			} else {
				fmt.Println(strings.Repeat("  ", t)+"+", b.label.t.String(), b.label.n)
			}
		} else {
			fmt.Println("+")
		}
		return false
	}, nil, func(c *Current, t int, index int, count int, b *Block, l *Label) {
		if l.IsData() {
			if l.IsLabelType(core.LABEL_LOCAL) {
				fmt.Println(strings.Repeat("  ", t)+"-", l.d.t.String(), l.n, l.d.localIndex)
			} else {
				fmt.Println(strings.Repeat("  ", t)+"-", l.d.t.String())
			}
		} else {
			fmt.Println(strings.Repeat("  ", t)+"-", l.t.String(), l.n)
		}
	})
}
